<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Benchmark</title>
    <style>
        body{
            font-family: sans-serif;
        }
        table td{
            padding: 1em 2em;
        }
        button{
            padding: 5px 10px;
        }
    </style>
</head>
<body>
<h2>Benchmark Comparison</h2>
<h4>Indexed Text: Movie Titles</h4>
<hr>
<div id="container"></div>
<hr>
Test rules: 1. no cache allowed, 2. no async allowed, 3. should return at least 8 matches for the query "The Spirit", 4. result should be ordered by relevance
<script src="../dist/flexsearch.light.js"></script>
<script src="https://cdn.jsdelivr.net/gh/nextapps-de/bulksearch@master/bulksearch.light.js"></script>
<script src="https://cdn.jsdelivr.net/gh/weixsong/elasticlunr.js@0.9.6/example/elasticlunr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lunr@2.3.5/lunr.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/kbrsh/wade@0.3.3/dist/wade.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/krisk/Fuse@3.3.0/dist/fuse.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/js-search@1.4.2/dist/umd/js-search.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/karussell/jsii@master/web/js/src/BitSet.js"></script>
<script src="https://cdn.jsdelivr.net/gh/karussell/jsii@master/web/js/src/JSii.js"></script>
<script src="https://gistcdn.githack.com/vlad-x/a25e0c5c1eeb6bf6aa38/raw/02d1a1703e4a99a7c733c85097f583579f6af4e2/bm25.js"></script>
<script src="https://rawcdn.githack.com/jeancroy/FuzzySearch/cbcdd8307d70a209b1cbf17a535158d5c21840d7/dist/FuzzySearch.min.js"></script>
<script src="../data/movies.js"></script>
<script>

    (function(){

        var bulksearch;
        var flexsearch;
        // var flexsearch_cache;
        // var flexsearch_worker;
        // var flexsearch_cache_scale;
        var elasticsearch;
        var lunrsearch;
        var wade;
        var fuse;
        var jssearch;
        var jsii;
        var bm25;
        var fuzzysearch;

        var tests = {

            flexsearch: {

                init: function(){

                    flexsearch = new FlexSearch({
                        encode: "icase",
                        tokenize: "strict",
                        threshold: 8,
                        resolution: 9,
                        depth: 1,
                        async: false,
                        cache: false,
                        worker: false
                    });
                },
                add: function(index, content){

                    flexsearch.add(index, content);
                },
                query: function(query){

                    return flexsearch.search(query);
                },
                loops: 350000
            },

            /*
            flexsearch_cache_unbound: {

                init: function(){

                    flexsearch_cache = new FlexSearch({
                        encode: "icase",
                        tokenize: "strict",
                        threshold: 9,
                        depth: 1,
                        async: false,
                        cache: true,
                        worker: false
                    });
                },
                add: function(index, content){

                    flexsearch_cache.add(index, content);
                },
                query: function(query){

                    return flexsearch_cache.search(query);
                },
                loops: 1000000
            },
            */

            /*
            flexsearch_cache_balanced: {

                init: function(){

                    flexsearch_cache_scale = new FlexSearch({
                        encode: "icase",
                        tokenize: "strict",
                        threshold: 9,
                        depth: 1,
                        async: false,
                        cache: 100,
                        worker: false
                    });
                },
                add: function(index, content){

                    flexsearch_cache_scale.add(index, content);
                },
                query: function(query){

                    return flexsearch_cache_scale.search(query);
                },
                loops: 1000000
            },
            */

            /*
            flexsearch_worker: {

                init: function(){

                    flexsearch_worker = new FlexSearch({
                        encode: "icase",
                        tokenize: "strict",
                        threshold: 9,
                        depth: 1,
                        async: true,
                        cache: false,
                        worker: 4
                    });
                },
                add: function(index, content){

                    flexsearch_worker.add(index, content);
                },
                query: async function(query){

                    return await new Promise(function(resolve){

                        flexsearch_worker.search(query, resolve);
                    });
                },
                loops: 5000
            },
            */

            bulksearch: {

                init: function(){

                    bulksearch = new BulkSearch({
                        type: "short",
                        encode: "icase",
                        multi: false,
                        async: false,
                        cache: false,
                        worker: false
                    });
                },
                add: function(index, content){

                    bulksearch.add(index, content);
                },
                query: function(query){

                    return bulksearch.search(query);
                },
                loops: 2800
            },

            elasticlunr: {

                init: function(){

                    elasticsearch = elasticlunr(function(){
                        this.setRef("id");
                        this.addField("content");
                    });
                },
                add: function(index, content){

                    elasticsearch.addDoc({id: index, content: content});
                },
                query: function(query){

                    return elasticsearch.search(query);
                },
                loops: 700
            },

            lunr: {

                init: function(){

                    lunrsearch = lunr(function(){
                        this.ref("id");
                        this.field("content");
                        for(var i = 0; i < data.length; i++){
                            this.add({id: i, content: data[i]});
                        }
                    });
                },
                add: function(index, content){},
                query: function(query){

                    return lunrsearch.search(query);
                },
                loops: 2400
            },

            wade: {

                init: function(){

                    wade = Wade(data.slice(0));
                },
                sort: function(a, b){

                    var sum = a.score - b.score;
                    return sum < 0 ? 1 : sum ? -1 : 0;
                },
                add: function(index, content){},
                query: function(query){

                    return wade(query).sort(this.sort);
                },
                loops: 10000
            },

            fuse: {

                init: function(){

                    var payload = [];

                    for(var i = 0; i < data.length; i++){
                        payload[i] = {id: i, content: data[i]};
                    }

                    fuse = new Fuse(payload, {
                        keys: ["content"],
                        id: "id",
                        shouldSort: true,
                        tokenize: true,
                        matchAllTokens: true,
                        threshold: 0.2
                    });
                },
                add: function(index, content){},
                query: function(query){

                    return fuse.search(query);
                },
                loops: 1
            },

            jssearch: {

                init: function(){

                    var payload = [];

                    for(var i = 0; i < data.length; i++){
                        payload[i] = {id: i, content: data[i]};
                    }

                    jssearch = new JsSearch.Search("id");
                    jssearch.addIndex("content");
                    jssearch.addDocuments(payload);
                },
                add: function(index, content){},
                query: function(query){

                    return jssearch.search(query);
                },
                loops: 8000
            },

            jsii: {

                init: function(){

                    var payload = [];

                    for(var i = 0; i < data.length; i++){
                        payload[i] = {id: i, text: data[i]};
                    }

                    jsii = new JSii();
                    jsii.feedDocs(payload);
                },
                add: function(index, content){},
                query: function(query){

                    return jsii.search(query);
                },
                loops: 100000
            },

            bm25: {

                init: function(){

                    bm25 = new BM25();

                    for(var i = 0; i < data.length; i++){
                        bm25.addDocument({id: i, body: data[i]});
                    }

                    bm25.updateIdf();
                },
                add: function(index, content){},
                query: function(query){

                    return bm25.search(query);
                },
                loops: 50
            },

            fuzzysearch: {

                init: function(){

                    fuzzysearch = new FuzzySearch({source:data.slice(0)});
                },
                add: function(index, content){},
                query: function(query){

                    return fuzzysearch.search(query);
                },
                loops: 4
            }
        };

        function init_container(target){

            var html = "<table>" +
                       "<tr>" +
                            "<th>Library&emsp;</th>" +
                            "<th>Benchmark (Single Phrase)&emsp;</th>" +
                            "<th>Benchmark (Multi Phrase)&emsp;</th>" +
                            "<th>Benchmark (Not Found)&emsp;</th>" +
                       "</tr>";

            for(var test in tests){

                if(tests.hasOwnProperty(test)){

                    html += "<tr>" +
                                "<td>" + test + "</td>" +
                                "<td id=\"test-" + test + "\">indexing ...</td>" +
                                "<td id=\"test-multi-" + test + "\"></td>" +
                                "<td id=\"test-notfound-" + test + "\"></td>" +
                            "</tr>"
                }
            }

            html += "</table>";

            target.innerHTML = html;
        }

        var is_mobile =  navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/);

        function init_tests(index, keys){

            var key = keys[index];
            var test = tests[key];

            test.init();

            if(is_mobile && (test.loops > 1)){

                test.loops = (test.loops / 5) >> 0;
            }

            for(var i = 0; i < data.length; i++){

                test.add(i, data[i]);
            }

            document.getElementById("test-" + key).textContent = "ready ...";

            if(++index < keys.length){

                setTimeout(function(){

                    init_tests(index, keys);

                }, 100);
            }
            else{

                setTimeout(function(){

                    start_tests(0, 0, keys);

                }, 1000);
            }
        }

        function start_tests(suite, index, keys){

            var phrase = suite === 0 ? "mermaid" : (suite === 1 ? "little mermaid" : "undefined");
            var key = keys[index];
            var test = tests[key];
            var loops = test.loops;
            var query = test.query;
            var start = Date.now();

            for(var x = 0; x < loops; x++){

                query(phrase);
            }

            var duration = Date.now() - start;

            console.log("[Suite " + suite + "] " + key + ":", duration);

            document.getElementById("test-" + (suite === 1 ? "multi-" : (suite === 2 ? "notfound-" : "")) + key).textContent = format_number(((1000 / duration * loops * 10 + 0.5) >> 0) / 10) + " op/s";

            if(++index >= keys.length){

                if(++suite < 3){

                    index = 0;
                }
            }

            if(index < keys.length){

                setTimeout(function(){

                    start_tests(suite, index, keys);

                }, 1000);
            }
        }

        init_container(document.getElementById("container"));

        setTimeout(function(){

            init_tests(0, Object.keys(tests));

        }, 100);

        function format_number(num){

            return ("" + num).replace(/(\d)(?=(\d{3})+\b)/g, '$1,');
        }

        // ---------------------------------------

        //window.tests = tests;
    })();
</script>
</body>
</html>
